#include <errno.h>
#include "base/mutex.h"
#include "base/logging.h"

Mutex::Mutex()
    : mutex_(PTHREAD_MUTEX_INITIALIZER),
      read_cond_(PTHREAD_COND_INITIALIZER),
      write_cond_(PTHREAD_COND_INITIALIZER),
      num_readers_(0),
      num_writers_(0),
      num_writers_waiting_(0) {
}

Mutex::~Mutex() {
  pthread_mutex_destroy(&mutex_);
  pthread_cond_destroy(&read_cond_);
  pthread_cond_destroy(&write_cond_);
}

void Mutex::Lock() {
  WriteLock();
}

void Mutex::Unlock() {
  WriteUnlock();
}

void Mutex::ReadLock() {
  // VLOG(1) << "Acquiring ReadLock";
  CHECK_EQ(pthread_mutex_lock(&mutex_), 0);
  // VLOG(1) << "Acquired ReadLock";
  // VLOG(1) << "Waiting ReadCond";
  // If there are writers waiting, writer is higher priority.
  // if (num_writers_ != 0 || num_writers_waiting_ != 0) {
  // Writer is lower priority.
  if (num_writers_ != 0) {
    pthread_cond_wait(&read_cond_, &mutex_);
  }
  // VLOG(1) << "ReadCond OK";
  ++num_readers_;
  CHECK_EQ(pthread_mutex_unlock(&mutex_), 0);
}

void Mutex::ReadUnlock() {
  CHECK_EQ(pthread_mutex_lock(&mutex_), 0);
  --num_readers_;
  if (num_readers_ == 0 && num_writers_waiting_ > 0) {
    pthread_cond_signal(&write_cond_);
  }
  CHECK_EQ(pthread_mutex_unlock(&mutex_), 0);
}
 
void Mutex::WriteLock() {
  // VLOG(1) << "Acquiring WriteLock";
  CHECK_EQ(pthread_mutex_lock(&mutex_), 0);
  // VLOG(1) << "Acquired WriteLock";
  // VLOG(1) << "Waiting WriteCond";
  ++num_writers_waiting_;
  if (num_readers_ != 0 || num_writers_ != 0) {
    pthread_cond_wait(&write_cond_, &mutex_);
  }
  // VLOG(1) << "WriteCond OK";
  ++num_writers_;
  --num_writers_waiting_;
  CHECK_EQ(pthread_mutex_unlock(&mutex_), 0);
}

void Mutex::WriteUnlock() {
  CHECK_EQ(pthread_mutex_lock(&mutex_), 0);
  --num_writers_;
  if (num_writers_waiting_ > 0) {
    pthread_cond_signal(&write_cond_);
  }
  pthread_cond_broadcast(&read_cond_);
  CHECK_EQ(pthread_mutex_unlock(&mutex_), 0);
} 
MutexLock::MutexLock(Mutex* mutex) : mutex_(mutex) {
  mutex_->Lock();
}

MutexLock::~MutexLock() {
  if (mutex_) {
    mutex_->Unlock();
  }
}

ReadLock::ReadLock(Mutex* mutex) : mutex_(mutex) {
  mutex_->ReadLock();
}

void ReadLock::Release() {
  mutex_->ReadUnlock();
  mutex_ = NULL;
}

ReadLock::~ReadLock() {
  if (mutex_) {
    mutex_->ReadUnlock();
  }
}

WriteLock::WriteLock(Mutex* mutex) : mutex_(mutex) {
  mutex_->WriteLock();
}

void WriteLock::Release() {
  mutex_->WriteUnlock();
  mutex_ = NULL;
}

WriteLock::~WriteLock() {
  if (mutex_) {
    mutex_->WriteUnlock();
  }
}
